// Copyright (c) 2019 Presto Labs Pte. Ltd.
// Author: xguo

#pragma once

#include <cmath>
#include <cstdio>
#include <limits>
#include <memory>
#include <stdexcept>
#include <string>
#include <utility>

#include "coin2/exchange/base/symbology/currency.h"
#include "coin2/exchange/base/symbology/product.h"
#include "coin2/exchange/base/symbology/symbol.h"
#include "coin2/exchange/base/symbology/tick_price.h"
#include "coin2/exchange/base/symbology/tick_qty.h"

namespace coin2::exchange::base::symbology {

class OrderFormatter {
 public:
  OrderFormatter(const TickPrice& tick_price, const TickQty& tick_qty)
      : tick_price_(tick_price), tick_qty_(tick_qty) {}

  double FloorPrice(double price) const { return tick_price_.RoundDown(price); }

  double CeilPrice(double price) const { return tick_price_.RoundUp(price); }

  double FloorQty(double qty) const { return tick_qty_.Floor(qty); }

  double CeilQty(double qty) const { return tick_qty_.Ceil(qty); }

  std::string FormatPrice(double price) const { return tick_price_.Format(price); }

  std::string FormatQty(double qty) const { return tick_qty_.Format(qty); }

 private:
  const TickPrice& tick_price_;
  const TickQty& tick_qty_;
};

class IProductInfo {
 public:
  virtual ~IProductInfo() = default;
  virtual const TickPrice& tick_price() const = 0;
  virtual const TickQty& tick_qty() const = 0;
  virtual const OrderFormatter& order_formatter() const = 0;
  virtual IProductInfo* Clone() const = 0;

  virtual bool is_inverse() const = 0;
  virtual double contract_value() const = 0;
  virtual double taker_fee_rate() const = 0;
  virtual double maker_fee_rate() const = 0;
  virtual double min_amount() const = 0;
  virtual double min_qty() const = 0;
  virtual std::string DebugString() const = 0;
  virtual const std::string& product_id() const = 0;

  virtual double first_tick_size() const = 0;
  virtual int64_t list_time() const = 0;
  virtual int64_t capture_time() const = 0;
  virtual double asset_multiplier() const = 0;
  virtual const std::string& asset() const = 0;
};

class ProductInfo : public IProductInfo {
 public:
  ~ProductInfo() override = default;
  explicit ProductInfo(const coin::proto::ProductInfoProto& product_info)
      : product_info_(product_info),
        first_tick_size_(0) {
    if (product_info.price_ticksize_size() == 1) {
      first_tick_size_ = product_info.price_ticksize()[0].value();
      tick_price_ = std::make_unique<FixedTickPrice>(product_info.price_ticksize()[0].value());
    } else {
      // variable tick price
      tick_price_ = std::make_unique<RangeVariableTickPrice>(product_info);
    }

    tick_qty_ = std::make_unique<FixedTickQty>(
        product_info.qty_ticksize()[0].value(),
        product_info.min_qty(),
        std::numeric_limits<double>::max(),
        product_info.min_amount());
    order_formatter_ = std::make_unique<OrderFormatter>(*tick_price_, *tick_qty_);
  }

  // --- constants ---
  double first_tick_size() const override { return first_tick_size_; }
  int64_t list_time() const override { return product_info_.list_time(); }
  int64_t capture_time() const override { return product_info_.capture_time(); }
  const TickPrice& tick_price() const override { return *tick_price_; }
  const TickQty& tick_qty() const override { return *tick_qty_; }
  const OrderFormatter& order_formatter() const override { return *order_formatter_; }

  bool is_inverse() const override { return product_info_.is_inverse(); }
  double contract_value() const override {
    return product_info_.has_contract_value() ? product_info_.contract_value() : NAN;}
  double taker_fee_rate() const override {
    return product_info_.has_taker_fee() ? product_info_.taker_fee() : NAN; }
  double maker_fee_rate() const override {
    return product_info_.has_maker_fee() ? product_info_.maker_fee() : NAN; }
  double min_amount() const override {
    return product_info_.has_min_amount() ? product_info_.min_amount() : NAN; }
  double min_qty() const override {
    return product_info_.has_min_qty() ? product_info_.min_qty() : NAN; }
  const std::string& product_id() const override {
    return product_info_.id();
  }
  const std::string& asset() const override {
    return product_info_.asset();
  };

  double asset_multiplier() const override {
    return product_info_.has_asset_multiplier() ? product_info_.asset_multiplier() : NAN; }

  std::string DebugString() const override { return product_info_.DebugString(); }
  IProductInfo* Clone() const override {
    return new ProductInfo(product_info_);
  }

 private:
  static double CalculateTickSize(const int precision) {
    CHECK(precision >= 0 && precision < 40) << "Precision out of range: " << precision;
    return std::pow(10., -precision);
  }

 private:
  const coin::proto::ProductInfoProto product_info_;
  std::unique_ptr<TickPrice> tick_price_;
  std::unique_ptr<TickQty> tick_qty_;
  std::unique_ptr<OrderFormatter> order_formatter_;
  double first_tick_size_;
};

class IProductHolder {
 public:
  virtual ~IProductHolder() = default;
  virtual const IProduct& product() const = 0;
  virtual const IProductInfo& product_info() const = 0;
  virtual IProductHolder* Clone() const = 0;
};

class ProductHolder : public IProductHolder {
 public:
  ProductHolder(std::unique_ptr<IProduct> product, std::unique_ptr<IProductInfo> product_info)
      : product_(std::move(product)), product_info_(std::move(product_info)) {}

  ProductHolder(const IProduct& product, const coin::proto::ProductInfoProto& product_info)
      : product_(product.Clone()), product_info_(std::make_unique<ProductInfo>(product_info)) {}

  const IProduct& product() const override { return *product_; }
  const IProductInfo& product_info() const override { return *product_info_; }
  IProductHolder* Clone() const override {
    return new ProductHolder(
        std::unique_ptr<IProduct>(product_->Clone()),
        std::unique_ptr<IProductInfo>(product_info_->Clone()));
  }

 private:
  std::unique_ptr<IProduct> product_;
  std::unique_ptr<IProductInfo> product_info_;
};

}  // namespace coin2::exchange::base::symbology
